package maze;

import java.awt.Color;

import com.threed.jpct.Loader;
import com.threed.jpct.Object3D;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;

/**
 * Represents a maze.
 * @author Team Deer
 * 
 */
public class Maze {

	private final World world;

	private final MazeStructure mazeStructure;

	private static final float cellWidth = 10f;

	// Constructor
	
	public Maze(final World world, final MazeStructure mazeStructure) {
		// Load textures
		final Texture wallTexture = new Texture(
				"Content\\Models\\Textures\\hedgeTexture.png");
		final Texture groundTexture = new Texture(
				"Content\\Models\\Textures\\groundTexture.png");
		final Texture marbleTexture = new Texture(
				"Content\\Models\\Textures\\marbleTexture.png");
		TextureManager.getInstance().addTexture("wallTexture", wallTexture);
		TextureManager.getInstance().addTexture("groundTexture", groundTexture);
		TextureManager.getInstance().addTexture("marbleTexture", marbleTexture);

		this.world = world;

		this.mazeStructure = mazeStructure;

		// Add the walls to world.
		this.generateWallObjects();

		// Add floor.
		this.generateFloorObject();

		final Object3D[] finishAreaModel = Loader.load3DS(
				"Content\\Models\\finishArea.3DS", 1);
		finishAreaModel[0].setAdditionalColor(new Color(150, 200, 200));
		finishAreaModel[1].setTexture("marbleTexture");
		finishAreaModel[2].setTexture("groundTexture");
		finishAreaModel[3].setTexture("wallTexture");

		for (final Object3D model : finishAreaModel) {
			model.build();
			model.translate(this.getFinishLinePosition());
		}
		this.world.addObjects(finishAreaModel);

	}

	// Methods
	
	private void generateWallObjects() {

		this.joinXWalls();
		this.joinYWalls();

	}

	/**
	 * Adds a wall witch start 
	 * at (x,y) with the length in 
	 * the direction wall type indicates
	 */
	private Object3D newWall(final int x, final int y, final Wall wall,
			final int length) {
	
		// Create Object
		final Object3D wallModel = new Object3D(8);
	
		// Wall thickness
		final float wallThickness = 50;
	
		// Coordinates
		final SimpleVector[] southWallPiece = new SimpleVector[] {
				new SimpleVector(Maze.cellWidth / wallThickness, -.7f
						* Maze.cellWidth, (-1 / 2f + length) * Maze.cellWidth),
				new SimpleVector(Maze.cellWidth / wallThickness, 0,
						(-1 / 2f + length) * Maze.cellWidth),
				new SimpleVector(Maze.cellWidth / wallThickness, 0, -1 / 2f
						* Maze.cellWidth),
				new SimpleVector(Maze.cellWidth / wallThickness, -.7f
						* Maze.cellWidth, -1 / 2f * Maze.cellWidth) };
		final SimpleVector[] northWallPiece = new SimpleVector[] {
				new SimpleVector(-Maze.cellWidth / wallThickness, -.7f
						* Maze.cellWidth, -1 / 2f * Maze.cellWidth),
				new SimpleVector(-Maze.cellWidth / wallThickness, 0, -1 / 2f
						* Maze.cellWidth),
				new SimpleVector(-Maze.cellWidth / wallThickness, 0,
						(-1 / 2f + length) * Maze.cellWidth),
				new SimpleVector(-Maze.cellWidth / wallThickness, -.7f
						* Maze.cellWidth, (-1 / 2f + length) * Maze.cellWidth) };
	
		// Alignment
		if ((wall.getIndex() & 1) == 1) {
			for (final SimpleVector v : southWallPiece) {
				v.rotateY((float) (-Math.PI / 2f));
			}
			for (final SimpleVector v : northWallPiece) {
				v.rotateY((float) (-Math.PI / 2f));
			}
		}
	
		addTriangles(wallModel, northWallPiece, southWallPiece, wallThickness,
				length);
	
		final SimpleVector offset = new SimpleVector(-Maze.cellWidth / 2f, 0, 0);
		offset.rotateY((float) (-Math.PI / 2 * wall.getIndex()));
		offset.add(new SimpleVector(Maze.cellWidth * y, 0, Maze.cellWidth * x));
		wallModel.translate(offset);
		wallModel.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
		wallModel.build();
		this.world.addObject(wallModel);
		return wallModel;
	}

	private void joinXWalls() {
		// Join X-walls:
		Integer northStart = null;
		Integer southStart = null;

		for (int y = 0; y < this.mazeStructure.getHeight(); y++) {
			for (int x = 0; x < this.mazeStructure.getWidth(); x++) {
				if (this.mazeStructure.cellHasWall(x, y, Wall.NORTH) && y == 0
						&& northStart == null) {
					northStart = x;
				}
				if (northStart != null) {
					if (!this.mazeStructure.cellHasWall(x, y, Wall.NORTH)) {
						newWall(northStart.intValue(), y, Wall.NORTH,
								(x - northStart.intValue()));
						northStart = null;
					} else if (x == (this.mazeStructure.getWidth() - 1)) {
						newWall(northStart.intValue(), y, Wall.NORTH,
								(x - northStart.intValue()) + 1);
						northStart = null;
					}
				}

				if (this.mazeStructure.cellHasWall(x, y, Wall.SOUTH)
						&& southStart == null) {
					southStart = x;
				}
				if (southStart != null) {
					if (!this.mazeStructure.cellHasWall(x, y, Wall.SOUTH)) {
						newWall(southStart.intValue(), y, Wall.SOUTH,
								(x - southStart.intValue()));
						southStart = null;
					} else if (x == (this.mazeStructure.getWidth() - 1)) {
						newWall(southStart.intValue(), y, Wall.SOUTH,
								(x - southStart.intValue()) + 1);
						southStart = null;
					}
				}
			}
		}
	}

	private void joinYWalls() {
		// Join Y-walls:
		Integer westStart = null;
		Integer eastStart = null;
		for (int x = 0; x < this.mazeStructure.getWidth(); x++) {
			for (int y = 0; y < this.mazeStructure.getHeight(); y++) {
				if (this.mazeStructure.cellHasWall(x, y, Wall.WEST) && x == 0
						&& westStart == null) {
					westStart = y;
				}
				if (westStart != null) {
					if (!this.mazeStructure.cellHasWall(x, y, Wall.WEST)) {
						newWall(x, westStart.intValue(), Wall.WEST,
								(y - westStart.intValue()));
						westStart = null;
					} else if (y == (this.mazeStructure.getHeight() - 1)) {
						newWall(x, westStart.intValue(), Wall.WEST,
								(y - westStart.intValue()) + 1);
						westStart = null;
					}
				}

				if (this.mazeStructure.cellHasWall(x, y, Wall.EAST)
						&& eastStart == null) {
					eastStart = y;
				}
				if (eastStart != null) {
					if (!this.mazeStructure.cellHasWall(x, y, Wall.EAST)) {
						newWall(x, eastStart.intValue(), Wall.EAST,
								(y - eastStart.intValue()));
						eastStart = null;
					} else if (y == (this.mazeStructure.getHeight() - 1)) {
						newWall(x, eastStart.intValue(), Wall.EAST,
								(y - eastStart.intValue()) + 1);
						eastStart = null;
					}
				}
			}
		}
	}

	private void addTriangles(final Object3D wallModel,
			final SimpleVector[] northWallPiece,
			final SimpleVector[] southWallPiece, final float wallThickness,
			final int length) {
		// Wall texture
		final int texID = TextureManager.getInstance().getTextureID(
				"wallTexture");

		// North
		wallModel.addTriangle(northWallPiece[0], 0, 0, northWallPiece[3],
				-length, 0, northWallPiece[1], 0, 1, texID);
		wallModel.addTriangle(northWallPiece[3], -length, 0, northWallPiece[2],
				-length, 1, northWallPiece[1], 0, 1, texID);
		// South
		wallModel.addTriangle(southWallPiece[0], 0, 0, southWallPiece[3],
				-length, 0, southWallPiece[1], 0, 1, texID);
		wallModel.addTriangle(southWallPiece[3], -length, 0, southWallPiece[2],
				-length, 1, southWallPiece[1], 0, 1, texID);
		// Short West
		wallModel.addTriangle(southWallPiece[3], 0, 0, northWallPiece[0], -2
				/ wallThickness, 0, southWallPiece[2], 0, 1, texID);
		wallModel.addTriangle(northWallPiece[0], -2 / wallThickness, 0,
				northWallPiece[1], -2 / wallThickness, 1, southWallPiece[2], 0,
				1, texID);
		// Short East
		wallModel.addTriangle(northWallPiece[3], 0, 0, southWallPiece[0], -2
				/ wallThickness, 0, northWallPiece[2], 0, 1, texID);
		wallModel.addTriangle(southWallPiece[0], -2 / wallThickness, 0,
				southWallPiece[1], -2 / wallThickness, 1, northWallPiece[2], 0,
				1, texID);
	}

	private void generateFloorObject() {
		final Object3D groundModel = new Object3D(2);

		final int texID = TextureManager.getInstance().getTextureID(
				"groundTexture");

		final SimpleVector[] v = new SimpleVector[] {
				new SimpleVector(-Maze.cellWidth / 2, 0, Maze.cellWidth / 2
						+ (this.mazeStructure.getWidth() - 1) * Maze.cellWidth),
				new SimpleVector(
						Maze.cellWidth / 2
								+ (this.mazeStructure.getHeight() - 1)
								* Maze.cellWidth, 0, Maze.cellWidth / 2
								+ (this.mazeStructure.getWidth() - 1)
								* Maze.cellWidth),
				new SimpleVector(
						Maze.cellWidth / 2
								+ (this.mazeStructure.getHeight() - 1)
								* Maze.cellWidth, 0, -Maze.cellWidth / 2),
				new SimpleVector(-Maze.cellWidth / 2, 0, -Maze.cellWidth / 2) };

		final int uvMul = 3;
		groundModel.addTriangle(v[0], 0, 0, v[3], this.mazeStructure.getWidth()
				* uvMul, 0, v[1], 0, this.mazeStructure.getHeight() * uvMul,
				texID);
		groundModel.addTriangle(v[3], this.mazeStructure.getWidth() * uvMul, 0,
				v[2], this.mazeStructure.getWidth() * uvMul,
				this.mazeStructure.getHeight() * uvMul, v[1], 0,
				this.mazeStructure.getHeight() * uvMul, texID);

		groundModel.build();

		this.world.addObject(groundModel);
	}

	/**
	 * 
	 * @return the maze structure
	 */
	public MazeStructure getMazeStructure() {
		return this.mazeStructure;
	}

	/**
	 * 
	 * @return The where the finishline should be
	 */
	public SimpleVector getFinishLinePosition() {
		return new SimpleVector(
				(this.mazeStructure.getWidth() - 1) * cellWidth, 0,
				this.mazeStructure.getHeight() * cellWidth);
	}
}
